#include "neuron.h"

#include <stdio.h>
#include <exception>
#include <stdexcept>
#include <iostream>

neuron::neuron() {
    neuron_id          = -1;
    neuron_study_error = 0.0;
    _psynapse          = null;
    _pcalcfunc         = null;
    study_buffer_index = null;
    setSynapseCount(0);
    setActivTiling(0.5);
}

neuron::neuron(const int syn_count) {
    neuron_id          = -1;
    neuron_study_error = 0.0;
    synapse_count      = 0;
    _psynapse          = null;
    _pcalcfunc         = null;
    setSynapseCount(syn_count);
    setActivTiling(0.5);
}

neuron::neuron(const neuron& data)
{
    activ_tiling       = data.activ_tiling;
    synapse_count      = data.synapse_count;
    _axon              = data._axon;
    _pcalcfunc         = data._pcalcfunc;
    study_buffer_index = data.study_buffer_index;
    neuron_id          = data.neuron_id;
    neuron_study_error = data.neuron_study_error;

    _psynapse = new synapse[synapse_count];
    for(ntype::synapsecount_t  isyncount = 0; isyncount < synapse_count; isyncount++)
    {
        _psynapse[isyncount] = data._psynapse[isyncount];
    }
}

neuron::~neuron(void){
    if(_psynapse != null && synapse_count > 0) {
        delete [] _psynapse;
        _psynapse = null;
    }
}

neuron& neuron::operator=(const neuron& data) {
    activ_tiling       = data.activ_tiling;
    synapse_count      = data.synapse_count;
    _axon              = data._axon;
    _pcalcfunc         = data._pcalcfunc;
    study_buffer_index = data.study_buffer_index;
    neuron_id          = data.neuron_id;
    neuron_study_error = data.neuron_study_error;

    _psynapse = new synapse[synapse_count];
    for(ntype::synapsecount_t  isyncount = 0; isyncount < synapse_count; isyncount++)
    {
        _psynapse[isyncount] = data._psynapse[isyncount];
    }
    return *this;
}

void neuron::setStudyBufferIndex(ntype::signalid_t index)
{
    study_buffer_index = index;
}

ntype::signalid_t neuron::getMaxSignalStudyBufferIndex() const
{
    return getSynapse(0).getMaxSignalStudyBufferIndex();
}

void neuron::setNeuronID(ntype::neuronid_t id) {
    neuron_id = id;
}

ntype::neuronid_t neuron::getNeuronID() const {
    return neuron_id;
}

void neuron::setActivTiling(double v) {
    activ_tiling = v;
}

double neuron::getActivTiling() {
    return activ_tiling;
}

void neuron::setSynapseCount(ntype::synapsecount_t count) {
    synapse_count = count;

    if(synapse_count > 0 && _psynapse != null) {
        delete [] _psynapse;
        _psynapse = null;
    }

    _psynapse = new synapse[synapse_count];
}

ntype::synapsecount_t neuron::getSynapseCount() const {
    return synapse_count;
}


void neuron::setCalcFunction(neuron_calc *pf) {
    _pcalcfunc = pf;
}

synapse &neuron::getSynapse(ntype::synapseid_t i) const {
    if(i < synapse_count) {
        return _psynapse[i];
    }
    //return synapse();
    throw std::overflow_error("get synapse out of range");
}

bool neuron::setSynapse(const synapse& syn, ntype::synapseid_t i){
    bool bret_value = false;
    if(i < synapse_count) {
        _psynapse[i] = syn;
        bret_value = true;
    } else {
        throw std::overflow_error("set synapse out of range");
    }
    return bret_value;
}

axon &neuron::getAxon(void) {
    return _axon;
}

const axon &neuron::getAxon(void) const {
    return _axon;
}

void neuron::setAxon(const axon& v){
    _axon = v;
}

/**
 * calculate forward signal
 */
double neuron::calc(void){
    ntype::synapsecount_t icount = 0;
    ntype::signal_t synapse_val = 0.0;
    double calc_value = 0.0;

    for(; icount < synapse_count; icount ++) {
        synapse_val += _psynapse[icount].getFSignal() * _psynapse[icount].getWeight();
    }

    synapse_val = synapse_val / (double)synapse_count;
    calc_value = _pcalcfunc->calc(synapse_val, activ_tiling);

    _axon.setFSignal(calc_value);
    return calc_value;
}


/**
 * calculate forward signal for study mode.
 * all cahnages are presented on the study branch
 */
double neuron::calc_study(void){
    ntype::synapsecount_t icount = 0;
    ntype::signal_t synapse_val = 0.0;
    double calc_value = 0.0;

    for(; icount < synapse_count; icount ++) {
        synapse_val += _psynapse[icount].getFSignalStudy(study_buffer_index) * _psynapse[icount].getWeight()/*.getWeightStudy(study_buffer_index)*/;
//        std::cout << "study_index: "<< study_buffer_index <<" fsig[study]: " << _psynapse[icount].getFSignalStudy(study_buffer_index) << " Weight: " << _psynapse[icount].getWeight() << std::endl;
    }

    synapse_val = synapse_val / (double)synapse_count;
    calc_value = _pcalcfunc->calc(synapse_val, activ_tiling);

    _axon.setFStudySignal(study_buffer_index, calc_value);
    return calc_value;
}

ntype::error_t neuron::getAxonStudyError()
{
    return getAxon().getError(study_buffer_index);
}


void neuron::setStudyNeuronError(ntype::error_t val)
{
  neuron_study_error = val;
}

ntype::error_t neuron::getStudyNeuronError() const
{
  return neuron_study_error;
}


/**
 * calculate back signal
 */
void neuron::calc_back(void){
    ntype::signal_t       signal = _axon.getBSignal();
    ntype::synapsecount_t icount = 0;
    ntype::synapsecount_t itmp_count = 0;
    ntype::signal_t       calc_value = 0.0;
    ntype::signal_t       tmp_val = 0.0;
    ntype::signal_t       tmp_syn_val = 0.;

    calc_value = _pcalcfunc->calc_back(signal, activ_tiling);
    for(icount  = 0; icount < synapse_count; icount ++){
        tmp_val = 0.0;
        for(itmp_count = 0; itmp_count < synapse_count; itmp_count ++) {
            if(itmp_count == icount)
                continue;
            tmp_val += (_psynapse[itmp_count].getFSignalStudy(study_buffer_index) * _psynapse[itmp_count].getWeight());
        }
        tmp_syn_val = calc_value - tmp_val;
        tmp_val = tmp_syn_val / _psynapse[icount].getWeight();
        _psynapse[icount].setBSignal(tmp_val);
    }
}

/**
 * calculate backward signal in the study mode
 */
void neuron::calc_back_study(void){
    ntype::signal_t        signal = _axon.getBStudySignal(study_buffer_index);
    ntype::synapsecount_t  icount = 0;
    ntype::synapsecount_t  itmp_count = 0;
    ntype::signal_t        calc_value = 0.0;
    ntype::signal_t        tmp_val = 0.0;

    calc_value = _pcalcfunc->calc_back(signal, activ_tiling);
    for(icount  = 0; icount < synapse_count; icount ++){
        tmp_val = 0.0;
        for(itmp_count = 0; itmp_count < synapse_count; itmp_count ++) {
            if(itmp_count == icount)
                continue;
            tmp_val += (_psynapse[itmp_count].getFSignalStudy(study_buffer_index) * _psynapse[itmp_count]/*.getWeightBack(study_buffer_index)*/.getWeight());
        }
        tmp_val = (calc_value * (ntype::signal_t)synapse_count) - tmp_val;
        tmp_val = tmp_val / _psynapse[icount]./*getWeightBack(study_buffer_index);*/ getWeight();
/*
printf("calc_back_study(...): neuron id: %d synapse back_value[%d] = %f\n",
                                   getNeuronID(),
                                icount,
                                tmp_val);
*/
        _psynapse[icount].setBSignalStudy(study_buffer_index, tmp_val);
    }
}

ntype::weight_t neuron::clac_back_synapse_w(ntype::synapseid_t syn_id)
{
    ntype::signal_t       signal = _axon.getBStudySignal(study_buffer_index);
    ntype::synapsecount_t itmp_count = 0;
    ntype::signal_t       calc_value = 0.0;
    ntype::weight_t       tmp_val = 0.0;
    ntype::weight_t       tmp_syn_val = 0.;

    calc_value = _pcalcfunc->calc_back(signal, activ_tiling);
    tmp_val = 0.0;
    for(itmp_count = 0; itmp_count < synapse_count; itmp_count ++) {
        if(itmp_count == syn_id)
            continue;
        tmp_val += (_psynapse[itmp_count].getFSignalStudy(study_buffer_index) * _psynapse[itmp_count].getWeight());
    }
    tmp_syn_val = (calc_value * (ntype::signal_t)synapse_count) - tmp_val;
    tmp_val = tmp_syn_val / _psynapse[syn_id].getFSignalStudy(study_buffer_index);
    return tmp_val;
}


#if 0
void neuron::calc_back_study_v01(void){
    double signal = _axon.getBStudySignal(study_buffer_index);
    int icount = 0;
    int itmp_count = 0;
    double calc_value = 0.0;
    double tmp_val = 0.0;
    double tmp_syn_val = 0.;

    calc_value = _pcalcfunc->calc_back(signal, activ_tiling);
    for(icount  = 0; icount < synapse_count; icount ++){
        tmp_val = 0.0;
        for(itmp_count = 0; itmp_count < synapse_count; itmp_count ++) {
            if(itmp_count == icount)
                continue;
            tmp_val += (_psynapse[itmp_count].getFSignalStudy(study_buffer_index) * _psynapse[itmp_count]/*.getWeightBack(study_buffer_index)*/.getWeight());
        }
        tmp_val = tmp_val / ((double)(synapse_count - 1));
        tmp_syn_val = calc_value - tmp_val;
        tmp_val = tmp_syn_val / _psynapse[icount]./*getWeightBack(study_buffer_index);*/ getWeight();
        _psynapse[icount].setBSignalStudy(study_buffer_index, tmp_val);
    }
}

#endif


